//Learned：
//1. 请求和响应的请求行信息当然不一样！
//2. 创建线程时，要在堆上开辟空间使用；不能单纯传址，因为一次循环后对象其实是销毁了的
//3. 分割了报头的一行后，记得将其擦除
//4. 请求静态资源不成功的主要原因：处理url的方式和输入url的格式要求有冲突。输入的url中以“/”代表根目录，而不是直接是目录名
//   主要还有一个误解：打开当前目录下的文件要么是 "wwwroot" 要么是 "./wwwroot" ；没有"/wwwroot"这回事（打开文件失败）！
#include "Socket.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <vector>
#include <fstream>
#include <sstream>
#include <unordered_map>

using std::vector;
using std::ifstream;
using std::unordered_map;

const string DefalutPath = "./wwwroot";
const string HomePage = "/index.html";
const string ErrPage = "/404.html";
const string Sep = "\r\n";

class HttpServer;
struct ThreadData
{
    ThreadData(int fd)
    :_sockfd(fd)
    {}

    int _sockfd;
   // HttpServer* _http_srv;
};

class HttpRequest
{
public:

    //假设读到的是一个完整独立的请求
    void Deserialize(string& package)   //正确分割？
    {
        while(1)
        {
            size_t pos = package.find(Sep);
            if(pos == string::npos)
            {
                break;
            }
            string temp = package.substr(0,pos);
            if(temp.empty()) break;    //证明读到了空行
            _header.push_back(temp);         
            package.erase(0,pos + Sep.size());     //注意不是加1，而是加分隔符的长度！
        }

    }

    void Parse()
    {
        string req_line = _header[0];
        stringstream ss(req_line);
        ss >> _method >> _url >> _http_version;
        
        if(_url == "/")
        {    
            _url = DefalutPath + HomePage;
        }
        else
        {
            _url = DefalutPath +  _url;
        }

        int pos = _url.rfind(".");
        if(pos == std::string::npos) exit(-1);

        _suffix = _url.substr(pos);
        cout << _suffix << endl;
    }

    void DebugPrint()
    {
        cout << "-----------------Debug-----------------"<<endl;
        cout << _method << " " <<_url << " " << _http_version << endl;
        for(auto& e : _header)
        {
            cout << e << endl;
        }
        cout << endl;
        cout << _context << endl;

        
    }
public:
    vector<string> _header;
    string _context;

    string _method;
    string _url;
    string _http_version;
    string _file_path;
    string _suffix;
};


class HttpServer
{
public:
    HttpServer(uint16_t port = 8080)
    :_port(port)
    {

    }

    static string ReadPathContents(const string& path = DefalutPath)   
    {

        // int fd = open(path.c_str(), O_RDONLY);      //这样读不了html文件？--> 可以！第一次后缀写成hmtl了！！！
        // if(fd < 0)
        // {
        //     return "";
        // }
        // char recvbuffer[10240];

        // read(fd,recvbuffer,sizeof(recvbuffer));
        // close(fd);

        // return string(recvbuffer);

        //c++读文件操作
        ifstream fin(path, ios::binary);
        if(!fin.is_open())   return "";

        //将读写位置移动到文件末尾，获取文件大小
        fin.seekg(0, ios_base::end);
        auto len = fin.tellg();
        //写回浏览器让其解释   
        fin.seekg(0, ios_base::beg);
        char recvbuffer[1024000] = {0};
        
        fin.read(recvbuffer,len);
        fin.close();

        return string(recvbuffer);

        //为什么上面不能显示图片但下面的代码可以？

        // std::ifstream in(path, std::ios::binary);
        // if(!in.is_open()) return "";

        // in.seekg(0, std::ios_base::end);
        // auto len = in.tellg();
        // in.seekg(0, std::ios_base::beg);

        // std::string content;
        // content.resize(len);

        // in.read((char*)content.c_str(), content.size());
        // in.close();

        // return content;
    }

    static void HttpHandler(int sockfd)
    {
        char buffer[10240];
        int n = recv(sockfd,buffer,sizeof(buffer),0);
        if(n > 0)
        {
            //cout << buffer;

            HttpRequest http_req;
            string package = string(buffer);
            http_req.Deserialize(package);
            http_req.Parse();
            http_req.DebugPrint();

            //响应
            string page_path = http_req._url;

            string text = ReadPathContents(page_path);
            if(text == "")
            {
                text = ReadPathContents(DefalutPath + ErrPage);
            }
            string resp_line = "HTTP/1.0 200 OK\r\n";
            string resp_head = "Content-Length: " + to_string(text.size()) + "\r\n"
                             + "Content-Type: " + _content_type_hash[http_req._suffix] + "\r\n"
                             + "Set-Cookie: " + "name=haha; domain=.example.com" + "\r\n";
            
            string blank_line = "\r\n";
            string msg = resp_line + resp_head + blank_line + text + "\r\n";
            
            send(sockfd,msg.c_str(),msg.size(),0);         
        }        
    }

    static void* ThreadRun(void* arg)
    {
        pthread_detach(pthread_self());

        ThreadData* td = static_cast<ThreadData*> (arg);

        HttpHandler(td->_sockfd);
        

        close(td->_sockfd); //注意顺序！
        delete td;  //注意释放内存！
        
    }

    void Start()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();

        log(Info, "server start...");

        while(1)
        {
            int fd = _listensock.Accpet(nullptr, nullptr);
            if(fd < 0)  continue;
            pthread_t tid;
            ThreadData* td = new ThreadData(fd);
            pthread_create(&tid,nullptr,ThreadRun,td);           
        }
    }

private:
    Sock _listensock;
    uint16_t _port;
    static unordered_map<string, string> _content_type_hash;
};

//标准写法应该是读取配置文件构建映射表！
unordered_map<string, string> HttpServer::_content_type_hash{{".html","text/html"}, {".png","image/png"}, {".jpg","image/jpeg"}};

